/*!
 * \file TcpSocket.cpp
 * \brief Implements the TcpSocket class.
 */

#include "TcpSocket.h"
using namespace Networking;

TcpSocket::TcpSocket( void ) : Socket()
{
	this->m_socket = socket( AF_INET, SOCK_STREAM, IPPROTO_IP );
	if ( INVALID_SOCKET == this->m_socket )
	{
		throw SocketException();
	}
}

TcpSocket::TcpSocket( SOCKET socket, struct sockaddr_in address ) : Socket()
{
	this->m_socket = socket;
	this->m_socketAddress = address;
	this->m_port = address.sin_port;
}

TcpSocket::~TcpSocket( void )
{
}

TcpSocket * TcpSocket::Accept( void )
{
	struct sockaddr_in clientEndpoint;

	#ifdef WIN32
		int addressLength = sizeof( struct sockaddr_in );
	#else
		socklen_t addressLength = sizeof( struct sockaddr_in );
	#endif

	SOCKET connect = accept( this->m_socket,
	                      (struct sockaddr *) &clientEndpoint,
	                      &addressLength );

	if ( INVALID_SOCKET == connect )
	{
		throw SocketException();
	}

	return new TcpSocket ( connect, clientEndpoint );
}

void TcpSocket::Listen ( unsigned short port )
{
	this->m_port = port;

	this->m_socketAddress.sin_family = AF_INET;
	this->m_socketAddress.sin_port = htons( m_port );
	this->m_socketAddress.sin_addr.s_addr = htonl( INADDR_ANY );

	int bindResult = bind( this->m_socket,
	                       ( struct sockaddr* ) &(this->m_socketAddress),
	                       sizeof(( this->m_socketAddress )));

	if ( SOCKET_ERROR == bindResult )
	{
		throw SocketException();
	}

	if ( SOCKET_ERROR == listen ( this->m_socket, 10 )) // backlog of 10
	{
		throw SocketException();
	}
}

void TcpSocket::Connect ( const char * name, unsigned short port )
{
	this->m_port = port;

	struct hostent * host = gethostbyname(name);
	if (!host)
	{
#ifndef WIN32
		herror("gethostbyname");
#endif
		throw SocketException ();
	}
	this->m_socketAddress.sin_family = AF_INET;
	this->m_socketAddress.sin_port = htons ( this->m_port );
	//this->m_socketAddress.sin_addr.s_addr = inet_addr ( name );
	this->m_socketAddress.sin_addr = *(struct in_addr*)host->h_addr;
	
	if ( SOCKET_ERROR == connect( this->m_socket,
	                              ( struct sockaddr* ) &this->m_socketAddress,
	                              sizeof( this->m_socketAddress )))
	{
		perror("connect");
		throw SocketException ();
	}
}


void TcpSocket::Send ( const char * buffer, int length )
{
	int sent = send ( this->m_socket, buffer, length, 0 );

	if ( SOCKET_ERROR == sent )
	{
		throw SocketException();
	}

	if ( sent != length )
	{
		throw "Failed to send all bytes";
	}
}

int TcpSocket::Receive ( char * buffer, int length )
{
	int received = recv ( this->m_socket, buffer, length, 0 );

	if ( SOCKET_ERROR == received )
	{
		throw SocketException ();
	}

	//if ( received == 0 )
	//{
	//	std::cout << "Socket shut down gracefully" << std::endl;
	//}

	return received;
}

void TcpSocket::ReceiveAll ( char * buffer, int length )
{
	int received = 0;
	int receivedSoFar = 0;

	do
	{
		received = this->Receive ( (buffer + receivedSoFar), length - receivedSoFar );
		if ( received == 0 )
		{
			// Socket was shutdown gracefully but we are expecting more...
			throw std::runtime_error( "Socket was shutdown" );
		}
		
		receivedSoFar += received;
		//std::cout << "[piece, so far, total] = [" << received << ", " << receivedSoFar << ", " << length << "]" << std::endl;
	}
	while ( receivedSoFar != length );
}

void TcpSocket::Shutdown ( TcpSocket::FlowToShutDown flow )
{
	int result = shutdown ( this->m_socket, flow );

	if ( SOCKET_ERROR == result )
	{
		throw SocketException ();
	}
}
